﻿using System.Diagnostics;
using CocoaAni.Net.WebSockets.Enums;
using CocoaAni.Net.WebSockets.Exceptions;
using CocoaAni.Net.WebSockets.Messages;

namespace CocoaAni.Net.WebSockets.Frames;

public interface IReceiveBuffer
{
    public byte[] Buffer { get; set; }
    public int HandleCursor { get; set; }
}

public class ReceiveBuffer : FrameHeader, IReceiveBuffer
{
    public bool IsCopyDataOnBuildMessage { get; set; }
    public WSOpcode? MessageOpcode { get; set; }
    public byte[] Buffer { get; set; }
    public int HandleCursor { get; set; }
    public int NextFrameWaitHandleLength { get; set; }
    public int BufferCanWriteCount => Buffer.Length - HandleCursor;
    public int PayloadSaveCursor { get; set; }
    public int PayloadSavedStartIndex { get; set; }
    public int PayloadSavedEndIndex => PayloadSavedStartIndex + PayloadReceivedLength;
    public bool IsComplete => HandleCursor != 0 && PayloadReceivedLength == PayloadLength;
    public int PayloadReceivedLength { get; set; }
    public bool IsSaveHeader { get; set; }
    public bool HasFrame => PayloadReceivedLength != 0;

    public ReceiveBuffer(byte[] buffer, int handleCursor, bool isSaveHeader) : base(buffer, handleCursor)
    {
        Buffer = buffer;
        HandleCursor = handleCursor;
        IsSaveHeader = isSaveHeader;
    }

    public ReceiveBuffer(int size) : base(new byte[size], 0)
    {
        Buffer = HeaderBuffer;
        IsSaveHeader = false;
    }

    public int AppendData(int dataLength)
    {
        if (IsComplete)
        {
            throw new NotSupportedException("当前帧已完成");
        }
        if (PayloadReceivedLength != 0)
        {
            return AppendBrokenPayload(dataLength);
        }

        var maxLenInFrame = PayloadLength + HeaderLength;
        var thisTimeHandleLength = dataLength > maxLenInFrame
            ? maxLenInFrame
            : dataLength;
        NextFrameWaitHandleLength = dataLength - thisTimeHandleLength;
        PayloadReceivedLength += thisTimeHandleLength - HeaderLength;

        var hasMask = HasMask;
        if (IsSaveHeader)
        {
            if (!hasMask)
            {
                HandleCursor += thisTimeHandleLength;
                return thisTimeHandleLength;
            }
            PayloadSaveCursor += HeaderLength;
        }
        PayloadSavedStartIndex = PayloadSaveCursor;
        var headerStart = HandleCursor;
        var payloadStart = headerStart + HeaderLength;
        var payloadEnd = headerStart + thisTimeHandleLength;
        var payloadSaveCursor = PayloadSaveCursor;
        if (!IsSaveHeader)
        {
            CopyHeaderFromBuffer();
        }
        var buf = Buffer;
        if (hasMask)
        {
            var maskId = 0;
            var masks = Masks!;
            for (var i = payloadStart; i < payloadEnd; i++, maskId++)
            {
                buf[payloadSaveCursor++] = (byte)(buf[i] ^ masks[maskId % 4]);
            }
        }
        else
        {
            for (var i = payloadStart; i < payloadEnd; i++)
            {
                buf[payloadSaveCursor++] = buf[i];
            }
        }
        HandleCursor += thisTimeHandleLength;
        PayloadSaveCursor = payloadSaveCursor;
        return thisTimeHandleLength;
    }

    public int AppendBrokenPayload(int dataLength)
    {
        if (PayloadReceivedLength >= PayloadLength)
        {
            throw new WebSocketException("当前帧以接收完成无法追加Payload！");
        }

        var maxHandleLength = PayloadReceivedLength - PayloadLength;
        var thisTimeHandleLength = dataLength > maxHandleLength
                ? maxHandleLength
                : dataLength;
        NextFrameWaitHandleLength -= dataLength - thisTimeHandleLength;
        PayloadReceivedLength += thisTimeHandleLength;

        if (IsSaveHeader)
        {
            if (!HasMask)
                return thisTimeHandleLength;
        }

        var payloadSaveCursor = PayloadSaveCursor;
        var payloadStart = payloadSaveCursor;
        var payloadEnd = payloadStart + thisTimeHandleLength;
        var buf = Buffer;
        if (HasMask)
        {
            var maskId = PayloadReceivedLength;
            var masks = Masks!;
            for (var i = payloadStart; i < payloadEnd; i++, maskId++)
            {
                buf[payloadSaveCursor++] = (byte)(buf[i] ^ masks[maskId % 4]);
            }
        }
        else
        {
            for (var i = payloadStart; i < payloadEnd; i++)
            {
                buf[payloadSaveCursor++] = buf[i];
            }
        }
        return thisTimeHandleLength;
    }

    public ReceiveBuffer CreateNextBuilder()
    {
        if (!IsComplete)
        {
            throw new InvalidOperationException("当前帧未完成！");
        }

        var builder = new ReceiveBuffer(Buffer, HandleCursor, IsSaveHeader)
        {
            PayloadSaveCursor = this.PayloadSaveCursor
        };
        return builder;
    }

    public Frame BuildFrame()
    {
        var frame = IsSaveHeader
            ? new ReceiveFrame(Buffer, HeaderStartIndex, PayloadSavedStartIndex)
            : new ReceiveFrame(HeaderBuffer, Buffer, PayloadSavedStartIndex);
        MessageOpcode ??= frame.Opcode;
        return frame;
    }

    public void DataMaskAndMove(byte[] buffer, int start, int end, byte[]? masks, int maskId, int move)
    {
        var hasMask = masks != null;
        switch (move)
        {
            case 0:
                if (masks == null)
                    return;
                for (var i = start; i < end; i++, maskId++)
                {
                    buffer[i] = (byte)(buffer[i] ^ masks[maskId % 4]);
                }
                break;

            case < 0:
                var newSavePos = start + move;
                if (masks != null)
                {
                    for (var i = start; i < end; i++, maskId++)
                    {
                        buffer[newSavePos++] = (byte)(buffer[i] ^ masks[maskId % 4]);
                    }
                }
                else
                {
                    for (var i = start; i < end; i++)
                    {
                        buffer[newSavePos++] = buffer[i];
                    }
                }
                break;

            case > 0:
                var newSaveEndPos = end + move - 1;
                var newSaveStartPos = start + move;
                end -= 1;
                if (masks != null)
                {
                    maskId += end - start;
                    for (var i = newSaveEndPos; i >= newSaveStartPos; i--, maskId--)
                    {
                        buffer[i] = (byte)(buffer[end--] ^ masks[maskId % 4]);
                    }
                }
                else
                {
                    for (var i = newSaveEndPos; i >= newSaveStartPos; i--)
                    {
                        buffer[i] = buffer[end--];
                    }
                }
                break;
        }
    }

    public Message BuildMessage()
    {
        Debug.Assert(MessageOpcode != null, nameof(MessageOpcode) + " != null");
        var message = new Message(this)
        {
            Opcode = MessageOpcode.Value
        };
        return message;
    }

    public void Reset()
    {
        ResetHeader();
        HeaderBuffer = Buffer;
        HandleCursor = 0;
        NextFrameWaitHandleLength = 0;
        PayloadSaveCursor = 0;
        PayloadSavedStartIndex = 0;
        PayloadReceivedLength = 0;
    }
}